#include "axes.h"

AxesCoor::AxesCoor(const Axes* axes)
{
  for (size_t i_dim = 0; i_dim < axes->get_n_dim(); i_dim++) {
    Axis* axis = axes->get_at(i_dim); 
    string i_dim_name = axis->get_name();    
    DynObject t(axis->get_tics_dyntype());
    tics.set(i_dim_name, t);
  }
}

AxesCoor::AxesCoor(const Axes* parent_axes, 
		   const vector<axis_index_t>& axis_indexes_coor)
{
  for (size_t i_dim = 0; i_dim < parent_axes->get_n_dim(); i_dim++) {
    Axis* axis = parent_axes->get_at(i_dim); 
    string i_dim_name = axis->get_name();    
    DynObject t(parent_axes->get_at(i_dim)->tics.key_at(axis_indexes_coor[i_dim]));
    tics.set(i_dim_name, t);
  }
}

size_t AxesCoor::get_n_dim() const
{
  return tics.size();
}

DynObject AxesCoor::get_at(size_t i_dim) const
{
  return tics.get_at(i_dim);
}

DynObject AxesCoor::get(const string& dim) const
{
  size_t i_dim = 0;
  if (tics.find(dim, &i_dim) == false) {
    throw Error(__FILE__, __LINE__, "AxesCoor::get: no such dim " + dim);
  };
  return tics.get_at(i_dim);
}

void AxesCoor::set_at(size_t i_dim, const DynObject& v)
{
  tics.set_at(i_dim, v);
}

void AxesCoor::set(const string& dim, const DynObject& v)
{
  size_t i_dim = 0;
  if (tics.find(dim, &i_dim) == false) {
    throw Error(__FILE__, __LINE__, "AxesCoor::set: no such dim " + dim);
  };
  tics.set_at(i_dim, v);
}

void AxesCoor::print(ostream& out) const
{
  out << "(";
  for (size_t i_dim = 0; i_dim < tics.size(); i_dim++) {
    out << tics.key_at(i_dim) << ": " << tics.get_at(i_dim) << ",";
  }
  out << ")";
}

// ...............................................................

AxesRange::AxesRange(const Axes* axes)
{
  this->axes = axes;
  for (size_t i_dim = 0; i_dim < axes->get_n_dim(); i_dim++) {
    string axis_name = axes->get_at(i_dim)->get_name();
    tic_ranges.push_back(axis_name, make_pair(DynObject(), DynObject()));
  }
}

size_t AxesRange::get_n_dim() const
{
  return tic_ranges.size();
}

void AxesRange::set_left(const string& dim, const DynObject& dd)
{
  pair<DynObject, DynObject>* pp = tic_ranges.get_ptr(dim);
  pp->first = dd;
}

void AxesRange::set_right(const string& dim, const DynObject& dd)
{
  pair<DynObject, DynObject>* pp = tic_ranges.get_ptr(dim);
  pp->second = dd;
}

void AxesRange::dump() const
{
  this->print(cout);
}

void AxesRange::print(ostream& out) const
{
  for (size_t i_dim = 0; i_dim < tic_ranges.size(); i_dim++) {
    out << tic_ranges.key_at(i_dim) << ": "
	<< tic_ranges.get_at(i_dim).first << " "
	<< tic_ranges.get_at(i_dim).second
	<< endl;
  }
}

vector<pair<axis_index_t, axis_index_t> > 
AxesRange::get_axis_indexes_range() const
{
  vector<pair<axis_index_t, axis_index_t> > ret(tic_ranges.size());

  for (size_t i_dim = 0; i_dim < tic_ranges.size(); i_dim++) {
    const Axis* axis = axes->get_at(i_dim);
    const pair<DynObject, DynObject>& tr = tic_ranges.get_at(i_dim);
    pair<axis_index_t, axis_index_t> r(0, 0);
    if (tr.first.is_none()) {
      r.first = 0;
    } else {
      if (axis->find_tic(tr.first, &r.first, 0) == false) {
	ostringstream msg;
	msg << "AxesRange::get_axis_indexes_range: "
	    << "can't find left tic at dim " << axis->get_name()
	    << " ";
	tr.first.print(msg);
	throw Error(__FILE__, __LINE__, msg.str());
      }
    }
    if (tr.second.is_none()) {
      r.second = axis->get_size();
    } else {
      if (axis->find_tic(tr.second, &r.second, 0) == false) {
	ostringstream msg;
	msg << "AxesRange::get_axis_indexes_range: "
	    << "can't find right tic at dim " << axis->get_name()
	    << " ";
	tr.second.print(msg);
	throw Error(__FILE__, __LINE__, msg.str());
      }
      r.second++;
    }
    ret[i_dim] = r;
  }
  return ret;
}

// ...............................................................

Axis::Axis(const string& axis_name, DynType* tics_dyntype)
{
  this->axis_name = axis_name;
  this->tics_dyntype = tics_dyntype;
}

Axis::~Axis()
{
}

size_t Axis::get_size() const 
{
  return tics.size();
}

const string& Axis::get_name() const
{
  return axis_name;
}

DynType* Axis::get_tics_dyntype() const
{
  return tics_dyntype;
}

// return true if element was added
bool Axis::add_tic(const DynObject& tic, data_index_t data_index) {
  if (tic.get_dyntype() != this->tics_dyntype) {
    throw Error(__FILE__, __LINE__, "Axis::add_tic: tic type mismatch");
  }
  return tics.add(tic, data_index);
}

void Axis::remove_tic(axis_index_t axis_index) 
{
  tics.remove(axis_index);
}

// return true if element was found
bool Axis::find_tic(const DynObject& tic,
		    axis_index_t* axis_index,
		    data_index_t* data_index) const 
{
  axis_index_t dummy;
  if (axis_index == 0) 
    axis_index = &dummy;
  bool ret = tics.find(tic, axis_index);
  if (ret && data_index) 
    *data_index = tics[*axis_index];
  return ret;
}

void Axis::print(ostream& out) const 
{
  out << "axis " << axis_name << endl;
  for (size_t i = 0; i < tics.size(); i++) {
    out << "(" << tics.key_at(i) << "," << tics[i] << "),";
  }
}

void Axis::save(ostream& out) const 
{
  out << axis_name << endl;
  tics_dyntype->save(out); out << endl;
  out << tics.size() << endl;
  for (size_t i = 0; i < tics.size(); i++) {
    const membuf_t& vb = tics.key_at(i).get_o_buf();
    vb.save(out);
    out << " " << tics[i] << endl;
  }
}

void Axis::load(istream& in) 
{
  size_t tics_size = 0;
  in >> tics_size;
  for (size_t i = 0; i < tics_size; i++) {
    in.ignore(1);
    membuf_t vb; vb.load(in);
    DynObject tic(tics_dyntype, vb);
    delete [] vb.buf;
    data_index_t data_index;
    in >> data_index;
    tics.add(tic, data_index);
  }
}

// ..............................................................

vector<axis_index_t> Axes::get_shape() const
{
  vector<axis_index_t> shape(axes.size());
  for (size_t i_dim = 0; i_dim < axes.size(); i_dim++) {
    shape[i_dim] = axes.get_at(i_dim)->get_size();
  }
  return shape;
}

size_t Axes::get_n_dim() const
{
  return axes.size();
}

void Axes::add_axis(const string& axis_name, Axis* axis)
{
  if (axes.has_key(axis_name)) {
    throw Error(__FILE__, __LINE__, 
		"Axes::add_axis: axis exists: " + axis_name);
  }
  axes.push_back(axis_name, axis);
}

Axis* Axes::get(const string& axis_name) const
{
  if (axes.has_key(axis_name) == false) {
    throw Error(__FILE__, __LINE__, "Axes::get: no such axis: " + axis_name);
  }
  return axes.get(axis_name);
}

Axis* Axes::get_at(size_t axis_index) const
{
  if (axis_index >= axes.size()) {
    throw Error(__FILE__, __LINE__, "Axes::get_at: axis_index is too high");
  }
  return axes.get_at(axis_index);
}

vector<axis_index_t> 
Axes::get_axis_indexes_coor(const AxesCoor& axes_coor) const
{
  if (axes.size() != axes_coor.get_n_dim()) {
    throw Error(__FILE__, __LINE__, "Axes::get_axes_index_coor: dim mismatch");
  }

  vector<axis_index_t> ret;
  for (size_t i_dim = 0; i_dim < axes.size(); ++i_dim) {
    DynObject t = axes_coor.get_at(i_dim);
    Axis* axis = axes.get_at(i_dim);
    axis_index_t ret_index = 0;
    if (axis->find_tic(t, &ret_index, 0) == false) {
      throw Error(__FILE__, __LINE__, "Axes::get_axes_index_coor: no tic in axis");
    }
    ret.push_back(ret_index);
  }
  return ret;
}

vector<data_index_t>
Axes::get_data_indexes_coor(const vector<axis_index_t>& axis_indexes_coor) const
{
  if (axes.size() != axis_indexes_coor.size()) {
    throw Error(__FILE__, __LINE__, "Axes::get_data_indexes_coor: n_dim mismathch");
  }

  vector<data_index_t> ret;
  for (size_t i_dim = 0; i_dim < axes.size(); ++i_dim) {
    Axis* axis = axes.get_at(i_dim);
    data_index_t data_index = axis->tics[axis_indexes_coor[i_dim]];
    ret.push_back(data_index);
  }
  return ret;
}

vector<pair<axis_index_t, axis_index_t> > 
Axes::get_axis_indexes_range(const AxesRange& tic_ranges) const
{
  vector<pair<axis_index_t, axis_index_t> > ret(tic_ranges.get_n_dim());
  for (size_t i_dim = 0; i_dim < tic_ranges.get_n_dim(); i_dim++) {
    Axis* axis = axes.get_at(i_dim);
    const pair<DynObject, DynObject>& tic_range = tic_ranges.tic_ranges.get_at(i_dim);
    pair<axis_index_t, axis_index_t> lr;
    if (tic_range.first.is_none()) {
      lr.first = 0;
    } else {
      if (axis->find_tic(tic_range.first, &lr.first, 0) == false) {
	throw Error(__FILE__, __LINE__, "Axes::get_axis_indexes_range: left tic not found");
      }
    }
    if (tic_range.second.is_none()) {
      lr.second = axis->tics.size();
    } else {
      if (axis->find_tic(tic_range.second, &lr.second, 0) == false) {
	throw Error(__FILE__, __LINE__, "Axes::get_axis_indexes_range: right tic not found");
      }
      lr.second++; // axis_indexes range looks like STL [begin, end)
    }
    ret[i_dim] = lr;
  }
  return ret;
}

bool Axes::does_coor_exist(const AxesCoor& coor) const
{
  if (axes.size() != coor.get_n_dim()) {
    throw Error(__FILE__, __LINE__, "Axes::does_coor_exist: dim mismatch");
  }
  
  for (size_t i_dim = 0; i_dim < this->get_n_dim(); i_dim++) {
    if (axes.get_at(i_dim)->find_tic(coor.get_at(i_dim), 0, 0) == false) {
      return false;
    }
  }
  return true;
}

void Axes::dump() const
{
  this->print(cerr);
}

void Axes::print(ostream& out) const
{
  out << axes.size() << " axes: " << endl;
  for (size_t i = 0; i < axes.size(); i++) {
    Axis* axis = axes.get_at(i);
    out << " " << axis->get_name() 
	<< "(" << axis->get_tics_dyntype()->get_name()
	<< ","
	<< axis->get_size() << ")";
    out << endl;
  }
}

void Axes::save(ostream& out) const
{
  out << axes.size() << endl;
  for (size_t i = 0; i < axes.size(); i++) {
    axes.get_at(i)->save(out);
  }
}

void Axes::load(istream& in)
{
  int nof_axis = 0;
  in >> nof_axis;
  for (int i = 0; i < nof_axis; i++) {
    string axis_name;
    in >> axis_name;
    string dyntypename = DynTypeManager::get()->load_and_register_dyntype(in);
    DynType* tics_dyntype = DynTypeManager::get()->get_dyntype(dyntypename);
    Axis* new_axis = new Axis(axis_name, tics_dyntype);
    new_axis->load(in);
    axes.push_back(axis_name, new_axis);
  }
}
